﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace server.app
{
    #region SpaceManager
    public sealed class SpaceManager
    {
        public int AllSpaceCount { get { return _spaceList.Count; } }

        List<Space> _spaceList = new List<Space>();
        List<List<Space>> _spaceGroup = new List<List<Space>>();

        comm.UniqueID _uid = new comm.UniqueID();
        World _world = null;

        const int THREAD_MAX_COUNT = 5;
        const int THREAD_SLEEP = 1000 / 40;

        public SpaceManager(World world)
        {
            _world = world;

            #region init threads
            for (int i = 0; i < THREAD_MAX_COUNT; i++)
            {
                _spaceGroup.Add(new List<Space>());
                var spaces = _spaceGroup[i];
                var timeStat = new comm.TimeStat();

                new Thread(new ThreadStart(() =>
                {
                    while (true)
                    {
                        timeStat.Update();
                        Space[] list = null;
                        lock (spaces)
                        {
                            list = spaces.ToArray();
                        }
                        foreach (var space in list)
                        {
                            space.Update(timeStat);
                        }

                        Thread.Sleep(THREAD_SLEEP);
                    }
                })).Start();
            }
            #endregion
        }

        public Space GetSpaceByPlayerId(string playerId)
        {
            var spaces = GetAllSpace();
            foreach (var space in spaces)
            {
                if (space.PlayerIsInSpace(playerId))
                {
                    return space;
                }
            }
            return null;
        }
        public Space CreateSpace(string mapId, string starId, string spaceId = null, Dictionary<string, comm.objst.DMap.Data.Obj> exObjDic = null)
        {
            if (spaceId == null)
            {
                spaceId = _uid.NewId();
            }
            var threadIndex = comm.MathHelper.RandomInt(0, _spaceGroup.Count);
            var space = _world.DataManager.CreateSpace(mapId, starId, spaceId, threadIndex, exObjDic);

            lock (_spaceList)
            {
                if (GetSpace(spaceId) == null)
                {
                    _spaceList.Add(space);

                    lock (_spaceGroup[threadIndex])
                    {
                        _spaceGroup[threadIndex].Add(space);
                    }
                    return space;
                }
            }
            return null;
        }
        public Space GetSpace(string spaceId)
        {
            var spaces = GetAllSpace();
            foreach (var space in spaces)
            {
                if (space.SpaceId == spaceId)
                {
                    return space;
                }
            }
            return null;
        }
        public Space[] GetAllSpace()
        {
            lock (_spaceList)
            {
                return _spaceList.ToArray();
            }
        }
    }
    #endregion

    #region SObject
    public class SObject : comm.world.BSObject
    {
        public Space Space { get { return _space; } }

        Space _space = null;

        public Action OnSetLocCallback = null;
        public Action OnBeginMoveCallback = null;
        public Action OnEndMoveCallback = null;
        public Action<comm.world.ObservedResultType, comm.world.ObservedResult> ReceiveResultOfObservedCallback = null;

        public SObject(Space space, comm.world.BSpace.LocManager locManager, string objectId)
            : base(space, locManager, objectId)
        {
            _space = space;
        }

        protected override void OnSetLoc(int oldX, int oldY, int newX, int newY)
        {
            if (OnSetLocCallback != null)
            {
                OnSetLocCallback();
            }
            base.OnSetLoc(oldX, oldY, newX, newY);
        }
        protected override void OnBeginMove(int fromX, int fromY, int toX, int toY, double maxMS)
        {
            if (OnBeginMoveCallback != null)
            {
                OnBeginMoveCallback();
            }
            base.OnBeginMove(fromX, fromY, toX, toY, maxMS);
        }
        protected override void OnEndMove()
        {
            if (OnEndMoveCallback != null)
            {
                OnEndMoveCallback();
            }
            base.OnEndMove();
        }
        public override void ReceiveResultOfObserved(comm.world.ObservedResultType type, comm.world.ObservedResult result)
        {
            if (ReceiveResultOfObservedCallback != null)
            {
                ReceiveResultOfObservedCallback(type, result);
            }
            base.ReceiveResultOfObserved(type, result);
        }
    }
    #endregion

    public class EnterEventArgs : EventArgs
    {
        private List<Object> _objList = null;
        public List<Object> ObjList
        {
            get
            {
                if (_objList == null)
                {
                    _objList = new List<Object>();
                }
                return _objList;
            }
            set { _objList = value; }
        }
    }

    public class LeaveEventArgs : EventArgs
    {
        private List<Object> _objList = null;
        public List<Object> ObjList
        {
            get
            {
                if (_objList == null)
                {
                    _objList = new List<Object>();
                }
                return _objList;
            }
            set { _objList = value; }
        }
    }

    //#region Space
    public abstract class Space : comm.world.BSpace
    {
        public World World { get; private set; }
        public int FPS { get { return _timeStat.FPS; } }
        public int ThreadIndex { get; private set; }

        public event EventHandler<EnterEventArgs> OnObjEnter;
        public event EventHandler<LeaveEventArgs> OnObjLeave;

        comm.TimeStat _timeStat = new comm.TimeStat();
        Dictionary<string, List<SObject>> _playerSObjDic = new Dictionary<string, List<SObject>>();

        public Space(World world, string mapId, string starId, string spaceId, int threadIndex, Dictionary<string, comm.objst.DMap.Data.Obj> exObjDic = null)
            : base(world.DefineFile, mapId, starId, spaceId, false, false, exObjDic)
        {
            World = world;
            ThreadIndex = threadIndex;
        }

        protected abstract void OnObjectEnter(Object[] enterObjs, Player player = null);
        protected abstract void OnObjectLeave(Object[] leaveObjs, Player player = null);

        public override string FindPath(int pathSize, int fromX, int fromY, int toX, int toY, Action<comm.world.FindTaskMode, comm.AStarNode[]> callback)
        {
            return "";
        }
        public override void CancelFindPath(string id)
        {
        }

        void CheckCreate(SObject sobj)
        {
            var playerId = Player.ObjectIdToPlayerId(sobj.ObjectId);
            if (playerId != null)
            {
                lock (_playerSObjDic)
                {
                    if (!_playerSObjDic.ContainsKey(playerId))
                    {
                        _playerSObjDic.Add(playerId, new List<SObject>());
                    }
                    _playerSObjDic[playerId].Add(sobj);
                }
            }
        }
        void CheckRemove(SObject sobj)
        {
            var playerId = Player.ObjectIdToPlayerId(sobj.ObjectId);
            if (playerId != null)
            {
                lock (_playerSObjDic)
                {
                    if (_playerSObjDic.ContainsKey(playerId))
                    {
                        var list = _playerSObjDic[playerId];
                        list.Remove(sobj);
                        if (list.Count == 0)
                        {
                            _playerSObjDic.Remove(playerId);
                        }
                    }
                }
            }
        }

        public comm.objst.MoveToResult[] PlayerBeginMoveObjects(Player player, comm.objst.MoveTo[] moveTos, long receiveTime, long timeOffset)
        {
            var useMS = (int)new TimeSpan(DateTime.Now.Ticks - receiveTime + timeOffset).TotalMilliseconds;
            var begin = DateTime.Now;
            var list = new List<comm.objst.MoveTo>();
            var resultList = new List<comm.objst.MoveToResult>();
            foreach (var moveTo in moveTos)
            {
                var sobj = GetSObject(moveTo.ObjectId);
                if (sobj != null)
                {
                    var result = new comm.objst.MoveToResult
                    {
                        ObjectId = moveTo.ObjectId,
                        CheckLoc = sobj.LocX == moveTo.FromX && sobj.LocY == moveTo.FromY,
                        CanEnter = true,
                        LocX = sobj.LocX,
                        LocY = sobj.LocY
                    };
                    resultList.Add(result);
                    if (sobj.MoveType == comm.objst.MoveType.FLIGHT)
                    {
                        moveTo.FromX = sobj.FromX;
                        moveTo.FromY = sobj.FromY;
                        list.Add(moveTo);
                        sobj.BeginMove(moveTo.ToX, moveTo.ToY, useMS);
                    }
                    else
                    {
                        result.CanEnter = sobj.Space.LocMgr.CheckPathCanEnter(moveTo.ToX, moveTo.ToY, sobj.PathSize);
                        if (result.CheckLoc && result.CanEnter)
                        {
                            list.Add(moveTo);
                            var steps = new int[][] { 
                            new int[] { moveTo.FromX, moveTo.FromY }, 
                            new int[] { moveTo.ToX, moveTo.ToY } };

                            sobj.BeginMoveSteps(steps, useMS);
                        }
                    }
                }
            }

            var ums = (int)(DateTime.Now - begin).TotalMilliseconds;
            useMS += ums;
            if (list.Count > 0)
            {
                GetAllPlayer(player.PlayerId).Send(comm.objst.TcpMessageType.ToClientSpaceObjectBeginMove, list.ToArray(), null, useMS);
            }

            return resultList.ToArray();
        }

        public SObject[] CreateSObjects(Object[] objs, int x, int y, Player player = null)
        {
            var createList = new List<SObject>();
            var enterList = new List<Object>();
            foreach (var obj in objs)
            {
                var sobj = new SObject(this, LocMgr, obj.ObjectId);
                if (AddBSObject(sobj))
                {
                    createList.Add(sobj);
                    enterList.Add(obj);
                    obj.SetSObject(sobj);
                    var dot = LocMgr.GenerateRimLoc(x, y, sobj.PathSize);
                    sobj.SetLoc(dot[0], dot[1]);
                    CheckCreate(sobj);
                }
            }
            OnObjectEnter(enterList.ToArray(), player);
            if (OnObjEnter != null)
            {
                OnObjEnter.BeginInvoke(this, new EnterEventArgs { ObjList = enterList }, null, null);
            }
            return createList.ToArray();
        }
        public int RemoveSObjects(Object[] objs, Player player = null)
        {
            var leaveList = new List<Object>();
            foreach (var obj in objs)
            {
                if (RemoveBSObject(obj.ObjectId))
                {
                    leaveList.Add(obj);
                    CheckRemove(obj.SObject);
                    obj.ClearSObject();
                }
            }
            OnObjectLeave(leaveList.ToArray(), player);
            OnObjLeave.BeginInvoke(this, new LeaveEventArgs { ObjList = leaveList }, null, null);
            return leaveList.Count;
        }

        public Object[] GetAllObject()
        {
            var sobjs = GetAllBSObject();
            var list = new List<Object>();
            foreach (var sobj in sobjs)
            {
                var obj = World.ObjectManager.GetObject(sobj.ObjectId);
                if (obj != null && obj.SObject != null && obj.SObject.ObjectId == sobj.ObjectId)
                {
                    list.Add(obj);
                }
            }
            return list.ToArray();
        }
        public bool PlayerIsInSpace(string playerId)
        {
            return _playerSObjDic.ContainsKey(playerId);
        }
        public SObject[] GetSObjectsByPlayerId(string playerId)
        {
            var arr = new SObject[] { };
            lock (_playerSObjDic)
            {
                if (_playerSObjDic.ContainsKey(playerId))
                {
                    arr = _playerSObjDic[playerId].ToArray();
                }
            }
            return arr;
        }
        public SObject GetSObject(string objectId)
        {
            var sobj = GetBSObject(objectId);
            if (sobj is SObject)
            {
                return (SObject)sobj;
            }
            return null;
        }
        public Player[] GetAllPlayer(string ignorePlayerId = null)
        {
            var list = new List<Player>();
            lock (_playerSObjDic)
            {
                foreach (var key in _playerSObjDic.Keys)
                {
                    var player = World.PlayerManager.GetPlayer(key);
                    if (player != null && player.PlayerId != ignorePlayerId)
                    {
                        list.Add(player);
                    }
                }
            }
            return list.ToArray();
        }
        public Player[] GetAllPlayer(Player ignorePlayer = null)
        {
            return GetAllPlayer(ignorePlayer != null ? ignorePlayer.PlayerId : null);
        }

        protected override void OnUpdate(comm.TimeStat timeStat)
        {
            _timeStat.Update();

            //while (_callQ.Count > 0)
            //{
            //    try
            //    {
            //        _callQ.Dequeue();
            //    }
            //    catch (Exception ex)
            //    {
            //        Console.WriteLine("Space _callQ err! " + ex.Message + " " + ex.StackTrace);
            //    }
            //}

            var allSObj = GetAllBSObject();
            foreach (var sobj in allSObj)
            {
                sobj.Update(timeStat);
            }
        }
    }
    //#endregion
}